/**
 * Copyright &copy; 2012-2014 <a href="https://github.cn.apestar">JeeSite</a> All rights reserved.
 */
package cn.apestar.common.web;

import java.beans.PropertyEditorSupport;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import javax.validation.Validator;

import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.ui.Model;
import org.springframework.validation.BindException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import cn.apestar.common.DataContants;
import cn.apestar.common.beanvalidator.BeanValidators;
import cn.apestar.common.mapper.JsonMapper;
import cn.apestar.common.utils.DateUtils;
import cn.apestar.common.utils.StringUtils;

/**
 * 控制器支持类
 * @author ThinkGem
 * @version 2013-3-23
 */
public abstract class BaseController {

    /**
     * 日志对象
     */
    protected Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * 管理基础路径
     */
    @Value("${adminPath}")
    protected String adminPath;

    /**
     * 前端基础路径
     */
    @Value("${frontPath}")
    protected String frontPath;

    /**
     * 前端URL后缀
     */
    @Value("${urlSuffix}")
    protected String urlSuffix;

    /**
     * 验证Bean实例对象
     */
    @Autowired
    protected Validator validator;

    /**
     * 为APP客户端进行请求Bean的校验，使用了JSR303标准的方式来校验<br/>
     * 如果有不合法的参数，则返回错误的提示消息，例如（字段：错误消息）的格式.
     * @author LBT
     * @param object 需要校验的Bean对象
     * @param groups 校验的分组对象
     * @return 错误的消息，如果没有错误，返回一个空内容的对象
     */
    protected List<String> beanValidatorForApp(Object object, Class<?>... groups) {
        try {
            BeanValidators.validateWithException(validator, object, groups);
        } catch (ConstraintViolationException ex) {
            return BeanValidators.extractPropertyAndMessageAsList(ex, ": ");
        }
        return new ArrayList<String>();
    }

    /**
     * 服务端参数有效性验证
     * @param object 验证的实体对象
     * @param groups 验证组
     * @return 验证成功：返回true；严重失败：将错误信息添加到 message 中
     */
    protected boolean beanValidator(Model model, Object object, Class<?>... groups) {
        try {
            BeanValidators.validateWithException(validator, object, groups);
        } catch (ConstraintViolationException ex) {
            List<String> list = BeanValidators.extractPropertyAndMessageAsList(ex, ": ");
            list.add(0, "数据验证失败：");
            addMessage(model, list.toArray(new String[] {}));
            return false;
        }
        return true;
    }

    /**
     * 服务端参数有效性验证
     * @param object 验证的实体对象
     * @param groups 验证组
     * @return 验证成功：返回true；严重失败：将错误信息添加到 flash message 中
     */
    protected boolean beanValidator(RedirectAttributes redirectAttributes, Object object, Class<?>... groups) {
        try {
            BeanValidators.validateWithException(validator, object, groups);
        } catch (ConstraintViolationException ex) {
            List<String> list = BeanValidators.extractPropertyAndMessageAsList(ex, ": ");
            list.add(0, "数据验证失败：");
            addMessage(redirectAttributes, list.toArray(new String[] {}));
            return false;
        }
        return true;
    }

    /**
     * 服务端参数有效性验证
     * @param object 验证的实体对象
     * @param groups 验证组，不传入此参数时，同@Valid注解验证
     * @return 验证成功：继续执行；验证失败：抛出异常跳转400页面。
     */
    protected void beanValidator(Object object, Class<?>... groups) {
        BeanValidators.validateWithException(validator, object, groups);
    }

    /**
     * 添加Model消息
     * @param message
     */
    protected void addMessage(Model model, String... messages) {
        StringBuilder sb = new StringBuilder();
        for (String message : messages) {
            sb.append(message).append(messages.length > 1 ? "<br/>" : "");
        }
        model.addAttribute("message", sb.toString());
    }

    /**
     * 添加Flash消息
     * @param message
     */
    protected void addMessage(RedirectAttributes redirectAttributes, String... messages) {
        StringBuilder sb = new StringBuilder();
        for (String message : messages) {
            sb.append(message).append(messages.length > 1 ? "<br/>" : "");
        }
        redirectAttributes.addFlashAttribute("message", sb.toString());
    }

    /**
     * 客户端返回JSON字符串
     * @param response
     * @param object
     * @return
     */
    protected String renderString(HttpServletResponse response, Object object) {
        return renderString(response, JsonMapper.toJsonString(object), "application/json");
    }

    /**
     * 客户端返回字符串
     * @param response
     * @param string
     * @return
     */
    protected String renderString(HttpServletResponse response, String string, String type) {
        try {
            response.reset();
            response.setContentType(type);
            response.setCharacterEncoding("utf-8");
            response.getWriter().print(string);
            return null;
        } catch (IOException e) {
            return null;
        }
    }

    /**
     * 参数绑定异常
     */
    @ExceptionHandler({BindException.class , ConstraintViolationException.class , ValidationException.class})
    public String bindException() {
        return "error/400";
    }

    /**
     * 授权登录异常
     */
    @ExceptionHandler({AuthenticationException.class})
    public String authenticationException() {
        return "error/403";
    }

    /**
     * 初始化数据绑定 1. 将所有传递进来的String进行HTML编码，防止XSS攻击 2. 将字段中Date类型转换为String类型
     */
    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        // String类型转换，将所有传递进来的String进行HTML编码，防止XSS攻击
        binder.registerCustomEditor(String.class, new PropertyEditorSupport() {

            @Override
            public void setAsText(String text) {
                setValue(text == null ? null : StringEscapeUtils.escapeHtml4(text.trim()));
            }

            @Override
            public String getAsText() {
                Object value = getValue();
                return value != null ? value.toString() : "";
            }
        });
        // Date 类型转换
        binder.registerCustomEditor(Date.class, new PropertyEditorSupport() {

            @Override
            public void setAsText(String text) {
                setValue(DateUtils.parseDate(text));
            }
            // @Override
            // public String getAsText() {
            // Object value = getValue();
            // return value != null ? DateUtils.formatDateTime((Date)value) :
            // "";
            // }
        });
    }

    /**
     * 获取默认的错误消息<br/>
     * 默认以JSON的格式显示，其中成功的消息为操作失败,标记为201，没有数据<br/>
     * 例如{message=操作失败, data=, errorCode=201}.
     * @author LBT
     * @return the default error message
     */
    protected String getDefaultErrorMessage() {
        Map<String, String> result = new HashMap<String, String>();
        result.put(DataContants.APP_JSON_KEY_RESULT_CODE, Integer.toString(DataContants.APP_RESULT_CODE_YC));
        result.put(DataContants.APP_JSON_KEY_MESSAGE, "操作失败");
        result.put(DataContants.APP_JSON_KEY_DATA, "");
        return result.toString();
    }

    /**
     * 获取操作(添加、删除、编辑)成功之后的消息<br/>
     * 默认以JSON的格式显示，其中成功的消息为success,成功的标记为0，没有数据<br/>
     * 例如{message=success, data=, errorCode=0}.
     * @author LBT
     * @return 操作成功之后的消息
     **/
    protected Object getDefaultSuccessMessage() {
        return getSuccessMessage(DataContants.APP_JSON_DEFAULT_SUCCESS_MSG, "");
    }

    /**
     * 获取操作(添加、删除、编辑)成功之后的消息<br/>
     * 默认以JSON的格式显示<br/>
     * 例如{message=success, data=, errorCode=0}.
     * @author LBT
     * @param message 自定义的成功消息
     * @param data 操作成功之后的数据
     * @return 操作成功之后消息
     */
    protected Object getSuccessMessage(String message, String data) {
        Map<String, String> result = new HashMap<String, String>();
        result.put(DataContants.APP_JSON_KEY_RESULT_CODE, Integer.toString(DataContants.APP_RESULT_CODE_SUCCESS));
        result.put(DataContants.APP_JSON_KEY_MESSAGE, message);
        result.put(DataContants.APP_JSON_KEY_DATA, data);
        return result;
    }

    /**
     * 获取操作(添加、删除、编辑)成功之后的消息<br/>
     * 默认以JSON的格式显示<br/>
     * 例如{message=success, data=, errorCode=0}.
     * @author LBT
     * @param message 自定义的成功消息
     * @param data 操作成功之后的数据
     * @return 操作成功之后消息
     */
    protected Object getSuccessMessageObject(String message, Object data) {
        if (StringUtils.isEmpty(message)) {
            message = "success";
        }
        Map<String, Object> result = new HashMap<String, Object>();
        result.put(DataContants.APP_JSON_KEY_RESULT_CODE, Integer.toString(DataContants.APP_RESULT_CODE_SUCCESS));
        result.put(DataContants.APP_JSON_KEY_MESSAGE, message);
        result.put(DataContants.APP_JSON_KEY_DATA, data);
        return result;
    }

    /**
     * 获取操作(添加、删除、编辑)成功之后的消息<br/>
     * 默认以JSON的格式显示<br/>
     * 例如{message=success, data=, errorCode=0}.
     * @author LBT
     * @param message 自定义的成功消息
     * @param data 操作成功之后的数据
     * @return 操作成功之后消息
     */
    protected Object getFileMessageObject(String message, Object data) {
        if (StringUtils.isEmpty(message)) {
            message = "success";
        }
        Map<String, Object> result = new HashMap<String, Object>();
        result.put(DataContants.APP_JSON_KEY_RESULT_CODE, Integer.toString(DataContants.APP_RESULT_CODE_YC));
        result.put(DataContants.APP_JSON_KEY_MESSAGE, message);
        result.put(DataContants.APP_JSON_KEY_DATA, data);
        return result;
    }

    /**
     * 生成无法参数的错误消息，由于接受到客户端传递来的请求参数可能会无效，因此需要生成消息来提示.
     * @author LBT
     * @param messages 错误的消息
     * @param data 错误的提示数据
     * @return 错误的提示消息，例如{message=[officeType: 办公类型的参数无效, contactSex: 性别的参数无效，1：男，2：女], data=, errorCode=1}
     */
    protected Object buildInvalidParameterErrorMsg(List<String> messages, String data) {
        Map<String, Object> result = new HashMap<String, Object>();
        result.put(DataContants.APP_JSON_KEY_RESULT_CODE, DataContants.APP_RESULT_CODE_PARAMETER_INVALIDATE);
        result.put(DataContants.APP_JSON_KEY_MESSAGE, messages);
        result.put(DataContants.APP_JSON_KEY_DATA, data);
        return result;
    }

    /**
     * 生成错误的消息.
     * @author LBT
     * @param errorCode 错误的消息异常码
     * @param message 错误的消息提示
     * @param data 错误的数据
     * @return 错误的消息
     */
    protected String buildErrorMessage(int errorCode, String message, String data) {
        Map<String, Object> result = new HashMap<String, Object>();
        result.put(DataContants.APP_JSON_KEY_RESULT_CODE, errorCode);
        result.put(DataContants.APP_JSON_KEY_MESSAGE, message);
        result.put(DataContants.APP_JSON_KEY_DATA, data);
        return result.toString();
    }
}
